﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Reflection;

namespace Hellquest
{
    public class GameObject
    {
        #region Properties
        public string Name { get; private set; }
        public bool Active
        {
            get { return mActive; }
            private set
            {
                mActive = value;
                foreach (Component comp in Components)
                    comp.IsActive = mActive;
            }
        }
        public Transform transform { get; set; }

        public GameObject Parent
        {
            set
            {
                if (value != this)
                {
                    if (mParent != null)
                    {
                        mParent.RemoveChild(this);
                    }

                    mParent = value;
                    if (value != null)
                    {
                        mParent.AddChild(this);
                    }
                }
            }
            get
            {
                return mParent;
            }
        }

        public List<GameObject> Childs
        {
            private set;
            get;
        }

        public List<GameObject> NewChilds
        {
            get { return childsToAdd; }
        }
        #endregion

        #region Fields
        private GameObject mParent;
        private List<Component> Components;
        private bool mActive;

        private List<GameObject> childsToAdd;
        private List<GameObject> childsToRemove;

        private List<Component> compsToAdd;
        private List<Component> compsToRemove;

        #endregion

        #region Component References
        private Sprite mSprite;
        private Rigidbody mRigidbody;
        private Collider mCollider;
        private Camera mCamera;

        public Sprite sprite { get { return mSprite; } set { mSprite = value; if (SpriteChanged != null) SpriteChanged(value); } }
        public Rigidbody rigidbody { get { return mRigidbody; } set { mRigidbody = value; } }
        public Collider collider { get { return mCollider; } set { mCollider = value; } }
        public Camera camera { get { return mCamera; } set { mCamera = value; } }
        #endregion

        public delegate void ComponentEvent(Component _sender);
        #region Component Events
        public event ComponentEvent SpriteChanged;
        #endregion

        #region Constructors

        public static GameObject Initialize(string _name, GameObject _parent = null)
        {
            GameObject newObject = new GameObject(_name, _parent, Vector2.Zero);
            newObject.Awake();
            return newObject;
        }

        public static GameObject Initialize(string _name, Vector2 _position, GameObject _parent = null)
        {
            GameObject newObject = new GameObject(_name, _parent, _position);
            newObject.Awake();
            return newObject;
        }

        public static GameObject Initialize(string _name, Dictionary<Type, Object[]> _initVals)
        {
            GameObject newObject = new GameObject(_name, null, Vector2.Zero);

            foreach (KeyValuePair<Type, Object[]> initVal in _initVals)
            {
                if (initVal.Key.IsSubclassOf(typeof(Component)))
                    typeof(GameObject).GetMethod("AddComponent").MakeGenericMethod(initVal.Key).Invoke(newObject, new object[] { initVal.Value });
            }
            newObject.Awake();
            return newObject;
        }

        public static GameObject Initialize(string _name, Vector2 _position, Dictionary<Type, object[]> _initVals)
        {
            GameObject newObject = new GameObject(_name, null, _position);

            foreach (KeyValuePair<Type, object[]> initVal in _initVals)
            {
                if (initVal.Key.IsSubclassOf(typeof(Component)))
                {
                    typeof(GameObject).GetMethod("AddComponent").MakeGenericMethod(initVal.Key).Invoke(newObject, new object[] { initVal.Value });
                }
            }

            newObject.Awake();
            return newObject;
        }

        public static GameObject Initialize(string _name, Vector2 _position, GameObject _parent, Dictionary<Type, object[]> _initVals)
        {
            GameObject newObject = new GameObject(_name, _parent, _position);

            foreach (KeyValuePair<Type, object[]> initVal in _initVals)
            {
                if (initVal.Key.IsSubclassOf(typeof(Component)))
                {
                    typeof(GameObject).GetMethod("AddComponent").MakeGenericMethod(initVal.Key).Invoke(newObject, new object[] { initVal.Value });
                }
            }

            newObject.Awake();
            return newObject;
        }

        private GameObject(string _name, GameObject _parent, Vector2 _position)
        {
            Components = new List<Component>();
            Childs = new List<GameObject>();

            compsToAdd = new List<Component>();
            compsToRemove = new List<Component>();
            childsToRemove = new List<GameObject>();
            childsToAdd = new List<GameObject>();

            Name = _name;
            Active = true;
            transform = new Transform(_position);
            transform.OnAdd(this);

            if (_name != "Origin" && _parent == null)
                Parent = SceneManager.Origin;
            else
                Parent = _parent;
        }

        #endregion

        #region Game Loop Methods

        public void Awake()
        {
            foreach (GameObject child in childsToAdd)
                Childs.Add(child);
            childsToAdd.Clear();

            foreach (GameObject child in childsToRemove)
                Childs.Remove(child);
            childsToRemove.Clear();

            foreach (Component comp in compsToAdd)
                Components.Add(comp);
            compsToAdd.Clear();

            foreach (Component comp in compsToRemove)
                Components.Remove(comp);
            compsToRemove.Clear();

            foreach (Component comp in Components)
                comp.Awake();

            foreach (GameObject child in Childs)
                child.Awake();
        }

        public void Update(GameTime _gameTime)
        {
            foreach (GameObject child in childsToAdd)
                Childs.Add(child);
            childsToAdd.Clear();

            foreach (GameObject child in childsToRemove)
                Childs.Remove(child);
            childsToRemove.Clear();

            foreach (Component comp in compsToAdd)
                Components.Add(comp);
            compsToAdd.Clear();

            foreach (Component comp in compsToRemove)
                Components.Remove(comp);
            compsToRemove.Clear();

            foreach (GameObject child in Childs)
                child.Update(_gameTime);

            if (Active)
            {
                foreach (Component comp in Components)
                    comp.Update(_gameTime);
                transform.Update(_gameTime);
            }
        }

        public void LateUpdate(GameTime _gameTime)
        {
            foreach (GameObject child in childsToAdd)
                Childs.Add(child);
            childsToAdd.Clear();

            foreach (GameObject child in childsToRemove)
                Childs.Remove(child);
            childsToRemove.Clear();

            foreach (Component comp in compsToAdd)
                Components.Add(comp);
            compsToAdd.Clear();

            foreach (Component comp in compsToRemove)
                Components.Remove(comp);
            compsToRemove.Clear();

            foreach (GameObject child in Childs)
                child.LateUpdate(_gameTime);

            if (Active)
            {
                foreach (Component comp in Components)
                    comp.LateUpdate(_gameTime);
                transform.LateUpdate(_gameTime);
            }
        }

        public void Draw()
        {
            if (Active)
            {
                foreach (Component comp in Components)
                    comp.Draw();

                foreach (GameObject child in Childs)
                    child.Draw();
            }
        }

        #endregion

        #region Component Handling
        /// <summary>
        /// You can pass an array of default values for the Component. Those values must match one of the Component's constructors
        /// </summary>
        public bool AddComponent<T>(object[] _defaultValues = null) where T : Component, new()
        {
            foreach (Component comp in Components)
            {
                if (comp.GetType() == typeof(T))
                {
                    return false;
                }
            }

            T newComponent;

            if (_defaultValues != null)
            {
                Type[] constrParams = new Type[_defaultValues.Length];
                for (int i = 0; i < _defaultValues.Length; i++)
                {
                    constrParams[i] = _defaultValues[i].GetType();
                }

                ConstructorInfo constructor = typeof(T).GetConstructor(constrParams);
                if (constructor == null)
                {
                    Console.WriteLine("Error: The default values didn't match the Component's constructor");
                    return false;
                }

                newComponent = (T)constructor.Invoke(_defaultValues);
            }
            else
            {
                newComponent = new T();
            }

            compsToAdd.Add(newComponent);
            newComponent.OnAdd(this);
            return true;
        }

        public T GetComponent<T>() where T : Component
        {
            foreach (Component comp in Components)
            {
                if (comp.GetType().IsSubclassOf(typeof(T)) || comp.GetType() == typeof(T))
                {
                    return comp as T;
                }
            }

            foreach (Component comp in compsToAdd)
            {
                if (comp.GetType().IsSubclassOf(typeof(T)) || comp.GetType() == typeof(T))
                {
                    return comp as T;
                }
            }
            Console.WriteLine("Component not found: " + typeof(T).Name);
            return null;
        }

        public List<T> GetComponentsInChildren<T>() where T : Component
        {
            List<T> retList = new List<T>();
            foreach (GameObject child in Childs)
            {
                if (child.GetComponent<T>() != null)
                    retList.Add(child.GetComponent<T>());
            }
            foreach (GameObject child in NewChilds)
            {
                if (child.GetComponent<T>() != null)
                    retList.Add(child.GetComponent<T>());
            }

            return retList;
        }

        public bool RemoveComponent<T>() where T : Component
        {
            Type typeToRemove = typeof(T);
            for (int i = Components.Count - 1; i >= 0; i--)
            {
                if (typeToRemove == Components[i].GetType())
                {
                    Components[i].OnRemove();
                    compsToRemove.Add(Components[i]);
                    return true;
                }
            }
            return false;
        }

        #endregion

        public void SetActive(bool _active)
        {
            foreach (Component comp in Components)
            {
                comp.IsActive = _active;
            }

            foreach (Component comp in compsToAdd)
            {
                comp.IsActive = _active;
            }
        }

        public void SetActiveRecursively(bool _active)
        {
            SetActive(_active);


            foreach (GameObject child in NewChilds)
                child.SetActiveRecursively(_active);

            foreach (GameObject child in Childs)
                child.SetActiveRecursively(_active);
        }

        private bool AddChild(GameObject _newChild)
        {
            if (!Childs.Contains(_newChild) && !childsToAdd.Contains(_newChild) && _newChild != this)
            {
                childsToAdd.Add(_newChild);
                return true;
            }
            Console.WriteLine("Error: The GameObject is already a child");
            return false;
        }

        private bool RemoveChild(GameObject _childToRemove)
        {
            if (Childs.Contains(_childToRemove))
            {
                childsToRemove.Add(_childToRemove);
                return true;
            }
            else
            {
                Console.WriteLine("Error: The GameObject is not a child");
                return false;
            }
        }

        public GameObject FindInChildren(string _name)
        {
            foreach (GameObject child in Childs)
            {
                if (child.Name == _name)
                    return child;
            }
            foreach (GameObject child in NewChilds)
            {
                if (child.Name == _name)
                    return child;
            }
            return null;
        }

        /// <summary>
        /// Don't call this yourself. The Scenemanager calls it, after a complete Update Circle
        /// </summary>
        public void Destroy()
        {
            for (int i = Components.Count - 1; i >= 0; i--)
            {
                Components[i].OnRemove();
                Components.RemoveAt(i);
            }

            for (int i = Childs.Count - 1; i >= 0; i--)
            {
                Childs[i].Destroy();
            }

            if (mParent != null)
                mParent.RemoveChild(this);
        }

        public void PrintPathRecursivly(List<string> _list)
        {
            string path = Name;
            GameObject parent = Parent;
            while (parent != null)
            {
                path = parent.Name + "/" + path;
                parent = parent.Parent;
            }
            _list.Add(path);

            foreach (GameObject child in Childs)
                child.PrintPathRecursivly(_list);
        }
    }
}
